package com.googlecode.cswish.util.crosssite;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.springframework.stereotype.Service;

import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.util.ClassUtil;
import com.googlecode.cswish.util.bean.ModelUtil;

@Service
public class CrossSiteUtil {

	private static final Log logger = LogFactory.getLog(CrossSiteUtil.class);
	
	@Resource
	private CacheService cacheService;
	
	public void convertToCrossSiteInstance(Object instance, Collection<Class> enabledClass) {
		enabledClass.add(instance.getClass());
		if (logger.isDebugEnabled()) {
			logger.debug("Begin convert instance: " + instance.getClass().getName());
		}
		convertToCrossSiteInstance(instance, "", enabledClass, new IdentityHashMap<Object, Object>());
		logger.debug("Conversion end");
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private void convertToCrossSiteInstance(Object instance, String parentPath,
			Collection<Class> enabledClass, Map<Object, Object> converted) {
		if (instance == null) {
			return ;
		}
		if (converted.containsKey(instance)) {
			return ;
		}
		Class checkClazz = instance.getClass();
		boolean pass = false;
		for (Class clazz : enabledClass) {
			if (clazz.isAssignableFrom(checkClazz)) {
				pass = true;
				break;
			}
		}
		if (!pass) {
//			if (logger.isDebugEnabled()) {
//				logger.debug("Skip property: " + parentPath + ", type: " + instance.getClass().getName());
//			}
			return ;
		}
		
		converted.put(instance, Boolean.TRUE);
//		if (logger.isDebugEnabled()) {
//			logger.debug("Convert property: " + parentPath + ", type: " + instance.getClass().getName());
//		}
		
		try {
			List<Field> fields = ModelUtil.getAllDeclaredFields(instance.getClass());
			for (Field field : fields) {
				final Class type = field.getType();
				if (Map.class.isAssignableFrom(type)) {
					field.setAccessible(true);
					Map oldValue = (Map)field.get(instance);
					if (oldValue != null) {
						// wrap with cross site class
						if ((HashMap.class.equals(type) || Map.class.equals(type))) {
							Object crossValue = cacheService.newCache(null, oldValue);
							field.set(instance, crossValue);
						} else if (BoundedConcurrentHashMap.class.equals(type)) {
							Object crossValue = cacheService.newBoundedMap((BoundedConcurrentHashMap)oldValue);
							field.set(instance, crossValue);
						} else {
							if (logger.isWarnEnabled()) {
								logger.warn("unknown map type: " + type + ", " + 
									field.getDeclaringClass().getName() + "." + field.getName());
							}
						}
						
						for (Object item : oldValue.entrySet()) {
							Map.Entry entry = (Map.Entry)item;
							String parentPrefix = parentPath + '.' + field.getName() + "['" + entry.getKey() + "']";
							convertToCrossSiteInstance(entry.getValue(), parentPrefix, enabledClass, converted);
						}
					}
				} else if (Collection.class.isAssignableFrom(type)) {
					field.setAccessible(true);
					Collection oldValue = (Collection)field.get(instance);
					if (oldValue != null) {
						// wrap with cross site class
						if (!Modifier.isFinal(field.getModifiers()) && (
								ArrayList.class.equals(type) || List.class.equals(type) || Collection.class.equals(type))) {
							Object crossValue = cacheService.newList((List)oldValue);
							field.set(instance, crossValue);
						}
						
						int index = 0;
						for (Object item : oldValue) {
							String parentPrefix = parentPath + '.' + field.getName() + '[' + index++ + ']';
							convertToCrossSiteInstance(item, parentPrefix, enabledClass, converted);
						}
					}
				} else if (!ClassUtil.isSimpleObj(type)) {
					field.setAccessible(true);
					Object oldValue = field.get(instance);
					String parentPrefix = parentPath + '.' + field.getName();
					convertToCrossSiteInstance(oldValue, parentPrefix, enabledClass, converted);
				}
			}
		} catch (Exception ex) {
			logger.error("Can't convert instance to cross site instance", ex);
		}
	}
}